package com.jxpanda.commons.toolkit;

import com.fasterxml.jackson.core.type.TypeReference;
import com.jxpanda.commons.constant.StringConstant;
import com.jxpanda.commons.toolkit.json.JsonKit;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 发送Http请求的工具
 * 设计思路，提供一个单例的client，方便使用的时候调用
 * 提供一个静态的init函数，方便调用方修改部分参数
 *
 * @author Panda
 */
@Slf4j
public class HttpKit {

    /**
     * 临时要用一个需要返回数据流的函数
     * 暂时只实现这个，以后有需要再重构
     */
    public static byte[] postForBytes(String url, Object param) {
        return httpClient()
                .sendAsync(new Http<byte[]>(Http.POST, url, param).request(),
                        HttpResponse.BodyHandlers.ofByteArray())
                .join()
                .body();
    }

    public static <T> T post(String url, Object param, Class<T> clazz) {
        return post(url, null, param, clazz);
    }

    public static <T> T post(String url, Map<String, String> headers, Object param, Class<T> clazz) {
        return send(new Http<>(Http.POST, url, headers, param, clazz));
    }

    public static <T> T post(String url, Object param, TypeReference<T> typeReference) {
        return post(url, null, param, typeReference);
    }

    public static <T> T post(String url, Map<String, String> headers, Object param, TypeReference<T> typeReference) {
        return send(new Http<>(Http.POST, url, headers, param, typeReference));
    }

    public static <T> T postForm(String url, Object param, Class<T> clazz) {
        return post(url, Map.of(Http.CONTENT_TYPE, Http.APPLICATION_FORM_URLENCODED), param, clazz);
    }

    public static <T> T postForm(String url, Object param, TypeReference<T> typeReference) {
        return post(url, Map.of(Http.CONTENT_TYPE, Http.APPLICATION_FORM_URLENCODED), param, typeReference);
    }


    public static <T> T get(String url, Class<T> clazz) {
        return get(url, null, null, clazz);
    }

    public static <T> T get(String url, Object param, Class<T> clazz) {
        return get(url, null, param, clazz);
    }

    public static <T> T get(String url, Map<String, String> headers, Object param, Class<T> clazz) {
        return send(new Http<>(Http.GET, url, headers, param, clazz));
    }


    public static <T> T get(String url, TypeReference<T> typeReference) {
        return get(url, null, null, typeReference);
    }

    public static <T> T get(String url, Object param, TypeReference<T> typeReference) {
        return get(url, null, param, typeReference);
    }

    public static <T> T get(String url, Map<String, String> headers, Object param, TypeReference<T> typeReference) {
        return send(new Http<>(Http.GET, url, headers, param, typeReference));
    }


    /**
     * 发送请求的函数
     * 泛型P是指入参的类型
     * 泛型R是指返回值的类型
     */
    @SneakyThrows
    private static <T> T send(Http<T> http) {
        if (log.isDebugEnabled()) {
            log.debug("[HTTP KIT REQUEST] " + JsonKit.toJson(http));
        }

        T response = httpClient()
                .sendAsync(http.request(), http.jsonHandler())
                .join()
                .body()
                .get();

        if (log.isDebugEnabled()) {
            log.debug("[HTTP KIT RESPONSE] " + JsonKit.toJson(response));
        }
        return response;
    }

    private static HttpClient httpClient() {
        return HttpClientSingleton.INSTANCE;
    }

    /**
     * 普通的httpClient，用于发送请求
     * 这个属性会在类创建的时候被单例初始化，以节省资源
     */
    private static final class HttpClientSingleton {
        private static final HttpClient INSTANCE = HttpClient.newBuilder()
                // 自动重定向（http -> https）
                .followRedirects(HttpClient.Redirect.NORMAL)
                .build();
    }

    /**
     * 预处理URL
     */
    @SuppressWarnings({"AlibabaClassNamingShouldBeCamel", "HttpUrlsUsage"})
    public static final class URL {

        private static final String PREFIX_HTTP = "http://";
        private static final String PREFIX_HTTPS = "https://";

        public static URI create(String url, String method, Object param) {
            if (!isAbsolute(url)) {
                url = PREFIX_HTTP + url;
            }
            if (Http.GET.equals(method) && param != null) {
                Map<String, Object> paramMap = JsonKit.toMap(param);
                String query = paramMap.entrySet().stream()
                        .map(entry -> entry.getKey() + StringConstant.EQUAL + entry.getValue())
                        .collect(Collectors.joining(StringConstant.AMPERSAND));

                if (url.indexOf(StringConstant.QUESTION_MARK) > 0) {
                    query = url.endsWith(StringConstant.QUESTION_MARK) ? query : (StringConstant.AMPERSAND + query);
                } else {
                    query = StringConstant.QUESTION_MARK + query;
                }
                url += query;
            }
            return URI.create(url);
        }

        private static boolean isHttp(String url) {
            return url.startsWith(PREFIX_HTTP);
        }

        private static boolean isHttps(String url) {
            return url.startsWith(PREFIX_HTTPS);
        }

        private static boolean isAbsolute(String url) {
            return isHttp(url) || isHttps(url);
        }

    }

    @Getter
    @Setter
    private static final class Http<T> {

        /**
         * 暂时没有发现其他地方需要用这两个常量
         * 所以先不写在constant包里
         */
        private static final String GET = "GET";
        private static final String POST = "POST";
        private static final String CONTENT_TYPE = "Content-Type";
        private static final String APPLICATION_JSON = "application/json";
        private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";

        private String method;
        private String url;
        private Map<String, String> headers;
        private Object param;
        private Class<T> resultType;
        private TypeReference<T> typeReference;

        public Http(String method, String url, Object param) {
            this.method = method;
            this.url = url;
            this.param = param;
        }

        public Http(String method, String url, Map<String, String> headers, Object param, Class<T> resultType) {
            this.method = method;
            this.url = url;
            this.headers = headers;
            this.param = param;
            this.resultType = resultType;
        }

        public Http(String method, String url, Map<String, String> headers, Object param, TypeReference<T> typeReference) {
            this.method = method;
            this.url = url;
            this.headers = headers;
            this.param = param;
            this.typeReference = typeReference;
        }

        public Map<String, String> getHeaders() {
            if (this.headers == null) {
                this.headers = new HashMap<>(8);
            }
            // 非GET请求，默认使用Json提交
            if (!GET.equals(this.method) && this.headers.isEmpty()) {
                this.headers.put(CONTENT_TYPE, APPLICATION_JSON);
            }
            return this.headers;
        }

        private boolean isFrom() {
            return this.getHeaders().getOrDefault(CONTENT_TYPE, StringConstant.BLANK).startsWith(APPLICATION_FORM_URLENCODED);
        }

        private boolean isJson() {
            return this.getHeaders().getOrDefault(CONTENT_TYPE, StringConstant.BLANK).startsWith(APPLICATION_JSON);
        }

        private HttpRequest request() {

            HttpRequest.Builder builder = HttpRequest
                    .newBuilder(URL.create(url, method, param));
            // 设置Headers
            this.getHeaders().forEach(builder::header);
            switch (method) {
                case GET:
                    builder.GET();
                    break;
                case POST:
                    HttpRequest.BodyPublisher bodyPublisher = HttpRequest.BodyPublishers.noBody();
                    if (param != null) {
                        if (isJson()) {
                            bodyPublisher = HttpRequest.BodyPublishers.ofString(JsonKit.toJson(param));
                        } else if (isFrom()) {
                            String json = JsonKit.toJson(param);
                            Map<String, String> paramMap = JsonKit.fromJson(json, new TypeReference<>() {
                            });
                            String paramString = paramMap.entrySet().stream()
                                    .map(entry -> entry.getKey() + StringConstant.EQUAL + entry.getValue())
                                    .collect(Collectors.joining(StringConstant.AMPERSAND));
                            bodyPublisher = HttpRequest.BodyPublishers.ofString(paramString);
                        }
                    }
                    builder.POST(bodyPublisher);
                    break;
                default:
            }
            return builder.build();
        }

        private HttpResponse.BodyHandler<Supplier<T>> jsonHandler() {
            return typeReference == null ? new JsonHandler<>(resultType) : new JsonHandler<>(typeReference);
        }


    }

    /**
     * 响应结果消息体的处理类，处理json消息
     * 这里返回一个Supplier对象
     * 原因是，如果返回的数据不是一个json格式，那么接口就会报错
     * 使用Supplier对象可以把这个转化过程延迟到调用Supplier的get函数才正式开始
     * 这样就可以处理阻塞操作
     */
    private static final class JsonHandler<T> implements HttpResponse.BodyHandler<Supplier<T>> {

        private Class<T> clazz;

        private TypeReference<T> typeReference;

        public JsonHandler(Class<T> clazz) {
            this.clazz = clazz;
        }

        public JsonHandler(TypeReference<T> typeReference) {
            this.typeReference = typeReference;
        }

        @Override
        public HttpResponse.BodySubscriber<Supplier<T>> apply(HttpResponse.ResponseInfo responseInfo) {
            HttpResponse.BodySubscriber<InputStream> upstream = HttpResponse.BodySubscribers.ofInputStream();
            return HttpResponse.BodySubscribers.mapping(upstream, this::covert);
        }

        @SuppressWarnings("unchecked")
        private Supplier<T> covert(InputStream inputStream) {
            AtomicReference<String> body = new AtomicReference<>(StringConstant.BLANK);
            return () -> {
                try {
                    byte[] bytes = inputStream.readAllBytes();
                    body.set(new String(bytes, StandardCharsets.UTF_8));

                    return typeReference == null ? JsonKit.jackson().readValue(bytes, clazz)
                            : JsonKit.jackson().readValue(bytes, typeReference);
                } catch (Exception e) {
                    return (T) body.get();
                }
            };
        }

    }

}
